package net.gdface.utils;

import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * 基于 {@link Cache}实现的访问限制<br>
 * cache中的数据过期后会自动被清除
 * @author guyadong
 *
 * @param <ID> 访问ID类型
 */
public abstract class AccessChecker<ID> {
	protected final Object NULL_OBJ = new Object();
	protected final Cache<ID,Object> cache;
	protected final ConcurrentMap<ID, Object> cacheMap;
	protected final long expireMills;
	/**
	 * 构造方法
	 * @param expireMills 缓存数据有效期(毫秒),必须大于0,过期自动从缓存删除
	 */
	protected AccessChecker(long expireMills) {
		checkArgument(expireMills > 0,"expireMills must grater than 0");
		this.expireMills = expireMills;
		cache = CacheBuilder.newBuilder()
				.expireAfterWrite(expireMills, TimeUnit.MILLISECONDS)
				.build();
		cacheMap = cache.asMap();
	}

	/**
	 * 基于 {@link AccessChecker}的地址限制检查
	 * @author guyadong
	 *
	 * @param <ID>
	 */
	public static class AddressChecker<ID> extends  AccessChecker<ID>{
		/**
		 * 构造方法
		 * @param expireMills 缓存数据有效期(毫秒),必须大于0,过期自动从缓存删除
		 */
		public AddressChecker(long expireMills) {
			super(expireMills);
		}
		/**
		 * 地址限制检查<br>
		 * 检查指定的{@code id}是否存在于缓存中,如果存在且映射的值与{@code value}不相等则抛出异常,
		 * 如果不存在则将{@code id-address}值对添加到缓存
		 * @param id 为{@code null}忽略
		 * @param address 为{@code null}忽略
		 * @throws AddressException
		 */
		public void checkAddress(ID id,Object address) throws AddressException{
			if(id != null && address != null){
				Object old = cacheMap.putIfAbsent(id, address);
				if(old != null && !old.equals(address)){
					throw new AddressException(String.format("%s mapped to  %s",id,old));
				}
			}
		}
	}
	/**
	 * 基于 {@link AccessChecker}的频率限制检查
	 * @author guyadong
	 *
	 * @param <ID> 名单ID类型
	 */
	public static class FreqChecker<ID> extends AccessChecker<ID>{
		/**
		 * 构造方法
		 * @param expireMills 缓存数据有效期(毫秒),必须大于0,过期自动从缓存删除
		 */
		public FreqChecker(long expireMills) {
			super(expireMills);
		}
		/**
		 * 频率限制检查<br>
		 * 检查指定的{@code id}是否存在于缓存中,如果存在则抛出异常,
		 * 如果不存在,则将,{@code id}添加到缓存
		 * @param id 为{@code null}忽略
		 * @throws FreqException
		 */
		public void checkFreq(ID id) throws FreqException{
			if(id != null ){
				// 系统时钟(毫秒)
				long clockMills = TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS);
				Long last = (Long) cacheMap.putIfAbsent(id, clockMills);
				if(last != null){
					throw new FreqException(String.format("%s locked caused by high frequency",id));
				}
			}
		}
		/**
		 * 频率限制检查<br>
		 * 检查指定的{@code id}是否存在于缓存中,如果存在且缓存中保存的时间戳与输入时间戳之差小于expireMills则抛出异常,
		 * 如果不存在,则将,{@code id}添加到缓存
		 * @param id 为{@code null}忽略
		 * @param timestamp 时间戳,为{@code null}时调用{@link #checkFreq(Object)}
		 * @param absCheck 为{@code true}时使用时间戳之差的绝对值来检查
		 * @throws FreqException
		 */
		public void checkFreq(ID id, Long timestamp, boolean absCheck) throws FreqException{
			if(null == timestamp){
				checkFreq(id);
			}else{
				if(id != null ){
					Long last = (Long) cacheMap.putIfAbsent(id, timestamp);
					if(last != null ){
						long diff = timestamp - last;
						if(absCheck){
							diff = Math.abs(diff);
						}
						if(diff < expireMills){
							throw new FreqException(String.format("%s locked caused by high frequency timestamp=%d last=%d", id,timestamp,last));
						}
					}
				}
			}
		}
		/**
		 * 频率限制检查<br>
		 * 检查指定的{@code id}是否存在于缓存中,如果存在且缓存中保存的时间戳与输入时间戳之差小于expireMills则抛出异常,
		 * 如果不存在,则将,{@code id}添加到缓存
		 * @param id 为{@code null}忽略
		 * @param timestamp 时间戳,为{@code null}时调用{@link #checkFreq(Object)}
		 * @throws FreqException
		 */
		public void checkFreq(ID id, Long timestamp) throws FreqException{
			checkFreq(id,timestamp,false);
		}
	}
	/**
	 * 基于采样统计的访问频率限制检查<br>
	 * 在指定的统计时间段内如果高频访问超出阀值则抛出异常
	 * @author guyadong
	 *
	 * @param <ID> 名单ID类型
	 */
	public static class FreqStatChecker<ID> extends AccessChecker<ID>{
		/** 统计缓存 */
		private final Cache<ID, AtomicInteger> statCache;
		private final ConcurrentMap<ID, AtomicInteger> statCacheMap;
		/** 高频访问阀值 */
		private final int hitThreshold;
		/**
		 * 构造方法
		 * @param sampIntervalMills 高频采样时间(毫秒),必须大于0,采样时间内超过1次访问即为高频访问
		 * @param statIntervalMills 高频统计时间(毫秒),必须大于{@code sampInternalMills}
		 * @param hitThreshold 统计时间段内最大高频访问计数,超过此值抛出异常
		 */
		public FreqStatChecker(long sampIntervalMills,long statIntervalMills,int hitThreshold) {
			super(sampIntervalMills);
			checkArgument(sampIntervalMills > 0,"sampIntervalMills must grater than 0");
			checkArgument(statIntervalMills > sampIntervalMills,"statIntervalMills must grater than sampIntervalMills");
			checkArgument(hitThreshold > 0,"hitThreshold must grater than 0");
			statCache = CacheBuilder.newBuilder()
					.expireAfterWrite(statIntervalMills, TimeUnit.MILLISECONDS)
					.build();
			statCacheMap = statCache.asMap();
			this.hitThreshold = hitThreshold;
		}
		/**
		 * 频率限制检查<br>
		 * 检查指定的{@code id}是否存在高频访问,如果存在则抛出异常,
		 * @param id 为{@code null}忽略
		 * @throws FreqException
		 */
		public void checkFreq(ID id) throws FreqException{
			if(id != null ){
				AtomicLong o1 = (AtomicLong) cacheMap.putIfAbsent(id, new AtomicLong(1));
				if(o1 != null){
					if(o1.incrementAndGet() == 2 ){
						// 第二次即为高频访问,增加高频访问统计计数
						AtomicInteger o2 = statCacheMap.putIfAbsent(id, new AtomicInteger(1));
						if(o2 != null){
							// 高频访问统计计数超出阀值抛出异常
							if(o2.incrementAndGet() >= hitThreshold){
								throw new FreqException(String.format("%s locked caused by high frequency",id));
							}
						}
					}
				}
			}
		}
	}
	/**
	 * 基于 {@link AccessChecker}的黑名单管理,黑名单中的数据到期自动删除
	 * @author guyadong
	 *
	 * @param <ID> 名单ID类型
	 */
	public static class BlacklistChecker<ID> extends AccessChecker<ID>{
		/**
		 * 构造方法
		 * @param expireMills 缓存数据有效期(毫秒),必须大于0,过期自动从缓存删除
		 */
		public BlacklistChecker(long expireMills) {
			super(expireMills);
		}
		/**
		 * 将指定的{@code id}添加到黑名单
		 * @param id
		 */
		public void add(ID id){
			cacheMap.putIfAbsent(id, NULL_OBJ);
		}
		/**
		 * 判断指定{@code id}是否在黑名单中
		 * @param id
		 * @return {@code id}在黑名单中返回{@code true},否则返回{@code false}
		 */
		public boolean contains(ID id){
			return null == id ? false : cacheMap.containsKey(id);
		}
		
		/**
		 * 黑名单限制检查<br>
		 * 检查指定的{@code id}是否存在于缓存中,如果存在则抛出异常
		 * @param id 为{@code null}忽略
		 * @throws BlacklistException
		 */
		public void checkLock(ID id) throws BlacklistException{
			if(contains(id)){
				throw new BlacklistException(String.format("%s is locked by blacklist",id));
			}
		}
	}
	public static class AccessCheckerException extends Exception{
		private static final long serialVersionUID = 1L;

		public AccessCheckerException() {
			super();
		}

		public AccessCheckerException(String arg0) {
			super(arg0);
		}
		
	}
	/**
	 * 访问地址限制异常
	 * @author guyadong
	 *
	 */
	public static class AddressException extends AccessCheckerException{
		private static final long serialVersionUID = 1L;

		public AddressException(String arg0) {
			super(arg0);
		}		
	}
	/**
	 * 访问频率限制异常
	 * @author guyadong
	 *
	 */
	public static class FreqException extends AccessCheckerException{
		private static final long serialVersionUID = 1L;

		public FreqException(String arg0) {
			super(arg0);
		}		
	}
	/**
	 * 黑名单限制异常
	 * @author guyadong
	 *
	 */
	public static class BlacklistException extends AccessCheckerException{
		private static final long serialVersionUID = 1L;

		public BlacklistException(String arg0) {
			super(arg0);
		}		
	}
}
